﻿using System;
using System.Collections;
using System.Linq.Expressions;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Nvigorate.Aspect;
using Nvigorate.Common;
using Nvigorate.Common.Reflection;
using Nvigorate.Data;
using Nvigorate.Extensions;
using Nvigorate.Relational.Mapping;
using Nvigorate.Relational.Mapping.Index;
using Nvigorate.Relational.Mapping.Interface;
using Nvigorate.Relational.Repository;
using Nvigorate.Relational.Visitors;
using Nvigorate.TestObjects;
using Nvigorate.Data.Query;

namespace Nvigorate.Test.Relational.Binding
{
    /// <summary>
    /// Summary description for DomainCrawlerTests
    /// </summary>
    [TestClass]
    public class DomainCrawlerTests
    {
        private IRepositoryMapIndex _mapIndex;

        public DomainCrawlerTests()
        {
            MapFake.PopulateMaps();
            _mapIndex = new MapIndex().GetRepositoryMapIndex("simple");
        }

        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        [TestMethod]
        public void TestQueryResult()
        {
            var salesDepartment = BuildDomain();
            var navigator = PersistenceDecorator.Create(salesDepartment).GetNavigator(_mapIndex);
            var visitor = new PersistVisitor(_mapIndex, false);
            navigator.Accept(visitor);

            var query = visitor.GetQuery();
            var commandText = query.Translate(new MSSQL9Provider());

            Assert.IsNotNull(commandText);
        }

        public class DTOBinder<DomainType, DTOType> 
            where DomainType : class, new()
            where DTOType : class, new()
        {
            private List<Tuple<string, string>> _domainToDTOPaths = new List<Tuple<string, string>>();

            public void Assign<PropertyType>(Expression<Func<DomainType, PropertyType>> domainPath, Expression<Func<DTOType, PropertyType>> dtoPath)
            {
                var domainExpression = domainPath.Body as MemberExpression;
                var domainAssignment = domainExpression.GetPropertyPath().BuildString(".");

                var dtoExpression = dtoPath.Body as MemberExpression;
                var dtoAssignment = dtoExpression.GetPropertyPath().BuildString(".");

                _domainToDTOPaths.Add(Tuple.Create(domainAssignment, dtoAssignment));
            }

            public DTOType GetDTOFromDomainInstance(DomainType domainInstance)
            {
                var newDTO = new DTOType();
                _domainToDTOPaths.ForEach(p => Reflector.Write(newDTO, p.Value2, Reflector.Read(domainInstance, p.Value1)));
                return newDTO;
            }

            public void RefreshDomainInstanceFromDTO(DomainType domainInstance, DTOType dtoInstance)
            {
                _domainToDTOPaths.ForEach(p => Reflector.Write(domainInstance, p.Value1, Reflector.Read(dtoInstance, p.Value2)));
            }
        }

        [TestMethod]
        public void TestDomainPersist()
        {
            var repository = new DatabaseRepository("simple");
            var salesDepartment = BuildDomain();
            try
            {
                //repository.Persist(salesDepartment);

                var newDepartment = repository.GetInstance(30, LoadInstruction<Department>.New().EagerLoad());

                Assert.IsNotNull(newDepartment);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                repository.Delete(salesDepartment, true);
            }
        }

        [TestMethod]
        public void TestDomainCrawl()
        {
            var salesDepartment = BuildDomain();
            var enumerable = GetPersistOrder(salesDepartment).ToList();
            string order = DelimitedBuilder.Construct(
                enumerable.Select(t => string.Format("{0} - {1}", t.Value1, t.Value2)), ", ");
            Assert.IsNotNull(enumerable);
        }

        [TestMethod]
        public void TestKeyManager()
        {
            var salesDepartment = BuildDomain();
            var repositoryMaps = new MapIndex().GetRepositoryMapIndex("simple");

            var keyManager = new PersistenceKeyManager(repositoryMaps);

            keyManager.RegisterType(typeof(Department));

            Assert.IsNotNull(keyManager);
        }

        public IEnumerable<Tuple<string, string>> GetPersistOrder(object instance)
        {
            return GetPersistOrder(instance, new List<Guid>());
        }

        public IEnumerable<Tuple<string, string>> GetPersistOrder(object instance, List<Guid> visited)
        {
            var instanceId = (Guid)(instance as IIdentifiable).InstanceId;
            if (visited.Any(v => v.Equals(instanceId)))
                return new List<Tuple<string, string>>();

            var map = _mapIndex.GetMap(instance.GetType());
            List<Tuple<string, string>> list= new List<Tuple<string, string>>();
            visited.Add(instanceId);

            var relationships = map.Relationships.OfType<OneToManyRelationship>();
            var dependencyRelationships = relationships
                .Where(r => r.Relationship == Relationship.Child || r.Relationship == Relationship.Pair);

            list.AddRange(
                    ProcessRelatives(
                        instance,
                        dependencyRelationships.Where(RelativeHasKey).Cast<ITargetRelationship>(),
                        visited
                    )
                );

            list.Add(Tuple.Create(instance.GetType().Name, Reflector.Read(instance, "Id").ToString()));

            list.AddRange(
                    ProcessRelatives(
                        instance,
                        relationships.Where(r => !RelativeHasKey(r)).Cast<ITargetRelationship>(),
                        visited
                    )
                );

            var manyToMany = map.Relationships.OfType<ManyToManyRelationship>().Cast<ITargetRelationship>();
            list.AddRange(ProcessRelatives(instance, manyToMany, visited));

            //var secondPassDependencies =
            //    relationships
            //        .Where(r => r.Relationship == Relationship.Parent)
            //        .Where(RelativeHasKey);

            //list.AddRange(
            //    ProcessRelatives(
            //        instance,
            //        secondPassDependencies.Cast<ITargetRelationship>(),
            //        new List<Guid>()
            //    )
            //);

            return list;
        }

        private IEnumerable<Tuple<string, string>> ProcessRelatives(object instance, IEnumerable<ITargetRelationship> relationships, List<Guid> visited)
        {
            return
                relationships
                    .Select(r => Reflector.Read(instance, r.RelativeProperty))
                    .Where(o => o != null)
                    .Select(o =>
                            {
                                if (Reflector.IsEnumerable(o.GetType()))
                                {
                                    return (o as IEnumerable).Select(e => GetPersistOrder(e, visited)).Flatten();
                                }
                                else
                                {
                                    return GetPersistOrder(o, visited);
                                }
                            }).Flatten();
        }

        public bool RelativeHasKey(OneToManyRelationship relationship)
        {
            var map = _mapIndex.GetMap(relationship.RelativeType);
            return relationship
                .Constraints
                .Values
                .Any(p => 
                    map.GetAssignmentsByPredicate(a => a.Target.Field == p && a.PrimaryKey).Count() > 0);
        }

        public Department BuildDomain()
        {
            var sales = new Department()
                            {
                                Id = 1,
                                Name = "Sales Department"
                            };

            var salesManager = new Job
                                  {
                                      Id = 1,
                                      Title = "Sales Manager",
                                      Description = "Motivates sales people",
                                      MinimumPay = 15.0m,
                                      MaximumPay = 20.0m
                                  };

            var salesPerson = new Job
                                  {
                                      Id = 2,
                                      Title = "Sales Rep",
                                      Description = "Sells stuff",
                                      MinimumPay = 10.0m,
                                      MaximumPay = 15.0m
                                  };

            var employees = new List<Employee>()
                                {
                                    new Employee()
                                        {
                                            Id = 1,
                                            SocialSecurity = "000-00-0001",
                                            FirstName = "Michael",
                                            LastName = "Scott",
                                            Department = sales,
                                            JobTitle = salesManager,
                                            DateOfBirth = DateTime.Parse("05/01/1972")
                                        },
                                    new Employee()
                                        {
                                            Id = 2,
                                            SocialSecurity = "000-00-0002",
                                            FirstName = "Dwight",
                                            LastName = "Shrute",
                                            Department = sales,
                                            JobTitle = salesPerson,
                                            DateOfBirth = DateTime.Parse("05/01/1972")
                                        },
                                    new Employee()
                                        {
                                            Id = 3,
                                            SocialSecurity = "000-00-0003",
                                            FirstName = "Jim",
                                            LastName = "Halpert",
                                            Department = sales,
                                            JobTitle = salesPerson,
                                            DateOfBirth = DateTime.Parse("05/01/1972")
                                        },
                                    new Employee()
                                        {
                                            Id = 4,
                                            SocialSecurity = "000-00-0004",
                                            FirstName = "Stanley",
                                            LastName = "TheManley",
                                            Department = sales,
                                            JobTitle = salesPerson,
                                            DateOfBirth = DateTime.Parse("05/01/1972")
                                        },
                                };

            var michaelScott = employees.First(e => e.Id == 1);
            sales.Manager = michaelScott;
            employees.Where(e => e.Id > 1).ForEach(e => michaelScott.Employees.Add(e));
            return sales;
        }
    }

    
}
